package com.wf.demo.job.netty;

import com.wf.demo.info.service.ParseService;
import com.wf.demo.info.util.ParseUtil;
import com.wf.demo.info.util.Result;
import com.wf.demo.job.util.RedisUtil;
import com.wf.demo.sys.constant.CommonConstant;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.ref.Reference;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @program: qingcheng
 * @author: XIONG CHUAN
 * @create: 2019-04-28 15:21
 * @description: netty服务端处理类
 **/

@Slf4j
@Component
@ChannelHandler.Sharable
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 管理一个全局map，保存连接进服务端的通道数量
     */
    private static final ConcurrentHashMap<ChannelId, ChannelHandlerContext> CHANNEL_MAP = new ConcurrentHashMap<>();

    //  将当前客户端连接 存入map   实现控制设备 下发参数
    public  static Map<String, ChannelHandlerContext> map = new HashMap<String, ChannelHandlerContext>();

    // 北斗数据解析服务,通过@Autowired获取Service层的Bean对象
    @Autowired
    ParseService parseService ;
    //为类声明一个静态变量，方便下一步存储bean对象；
    private static  NettyServerHandler nettyServerHandler;
    @PostConstruct //通过@PostConstruct实现初始化bean之前进行的操作
    public void init() {
        nettyServerHandler = this;
        nettyServerHandler.parseService = this.parseService;
        // 初使化时将已静态化的parseService实例化
    }

    /**
     * @param ctx
     * @author xiongchuan on 2019/4/28 16:10
     * @DESCRIPTION: 有客户端连接服务器会触发此函数
     * @return: void
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {

        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();

        String clientIp = insocket.getAddress().getHostAddress();
        int clientPort = insocket.getPort();

        //获取连接通道唯一标识
        ChannelId channelId = ctx.channel().id();
            /**
             * 把连接通道可以储存起来，5G的时候需要
             *
             * */
        System.out.println();
        //如果map中不包含此连接，就保存连接
        if (CHANNEL_MAP.containsKey(channelId)) {
            log.info("客户端【" + channelId + "】是连接状态，连接通道数量: " + CHANNEL_MAP.size());
        } else {
            //保存连接
            CHANNEL_MAP.put(channelId, ctx);

            log.info("客户端【" + channelId + "】连接netty服务器[IP:" + clientIp + "--->PORT:" + clientPort + "]");
            log.info("连接通道数量: " + CHANNEL_MAP.size());
        }
    }

    /**
     * @param ctx
     * @author xiongchuan on 2019/4/28 16:10
     * @DESCRIPTION: 有客户端终止连接服务器会触发此函数
     * @return: void
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {

        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();

        String clientIp = insocket.getAddress().getHostAddress();

        ChannelId channelId = ctx.channel().id();

        //包含此客户端才去删除
        if (CHANNEL_MAP.containsKey(channelId)) {
            //删除连接
            CHANNEL_MAP.remove(channelId);

            System.out.println();
            log.info("客户端【" + channelId + "】退出netty服务器[IP:" + clientIp + "--->PORT:" + insocket.getPort() + "]");
            log.info("连接通道数量: " + CHANNEL_MAP.size());
        }
    }

    /**
     * @param ctx
     * @author xiongchuan on 2019/4/28 16:10
     * @DESCRIPTION: 有客户端发消息会触发此函数
     * @return: void
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception {

        System.out.println();
        log.info("加载客户端报文......");
        log.info("【" + ctx.channel().id() + "】" + " :" + msg);

      //获取客户端的请求地址url  取到的值为客户端的 ip+端口号
        String url=ctx.channel().remoteAddress().toString();//设备请求地址（个人将设备的请求地址当作 map 的key）
        log.info("tcp客户端的url是"+url);
        if(map.get(url)!=null){//如果不为空就不存

        }else{//否则就将当前的设备ip+端口存进map  当做下发设备的标识的key
            map.put(url, ctx);
        }
        //msg为接收到的客户端传递的数据
        ByteBuf byteBuf = (ByteBuf) msg;
        byte[] receiveMsgBytes = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(receiveMsgBytes);
        String result= ParseUtil.printHexString(receiveMsgBytes);
        log.info("接收到tcp客户端的数据为"+result);

        //设备请求的 服务器端的地址   用作监听设备请求的那个端口
        String servicePort=ctx.channel().localAddress().toString();
        System.out.println("向："+servicePort.substring(servicePort.length()-4)+" 端口写入数据");

        if(servicePort.substring(servicePort.length()-4).equals("7000")) { //北斗端口，将北斗端口的tcp客户端ip+端口存到redis中，方便下发信息
            //解析客户端数据并储存
            Result parseResult= nettyServerHandler.parseService.processing(result,url);
            log.info("解析北斗parseService完成......");
            log.info(parseResult.getMessage());
            //响应客户端
            this.channelWrite(ctx.channel().id(),parseResult.getMessage() );
            //北斗端口的tcp客户端ip+端口存到redis中  controller层 通过设备redis查询此地址   取到map种存入的 ChannelHandlerContext 实现数据下发
        }else if(servicePort.substring(servicePort.length()-4).equals("7001")){
            //解析客户端数据并储存,并且对于5g,会将url保存到每个设备中
            Result parseResult= nettyServerHandler.parseService.processing5G(result,url);
            log.info("解析5GparseService完成......");
            log.info(parseResult.getMessage());
            //响应客户端
            this.channelWrite(ctx.channel().id(),parseResult.getMessage() );
            //ip地址存到
        }
        ReferenceCountUtil.release(byteBuf);   //手动释放
    }

    /**
     * @param msg        需要发送的消息内容
     * @param channelId 连接通道唯一id
     * @author xiongchuan on 2019/4/28 16:10
     * @DESCRIPTION: 服务端给客户端发送消息
     * @return: void
     */
    public void channelWrite(ChannelId channelId, Object msg) throws Exception {

        ChannelHandlerContext ctx = CHANNEL_MAP.get(channelId);

        if (ctx == null) {
            log.info("通道【" + channelId + "】不存在");
            return;
        }

        if (msg == null && msg == "") {
            log.info("服务端响应空的消息");
            return;
        }

        //将客户端的信息直接返回写入ctx
        ctx.write(msg);
        //刷新缓存区
        ctx.flush();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

        String socketString = ctx.channel().remoteAddress().toString();

        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                log.info("Client: " + socketString + " READER_IDLE 读超时");
                ctx.disconnect();
            } else if (event.state() == IdleState.WRITER_IDLE) {
                log.info("Client: " + socketString + " WRITER_IDLE 写超时");
                ctx.disconnect();
            } else if (event.state() == IdleState.ALL_IDLE) {
                log.info("Client: " + socketString + " ALL_IDLE 总超时");
                ctx.disconnect();
            }
        }
    }

    /**
     * @param ctx
     * @author xiongchuan on 2019/4/28 16:10
     * @DESCRIPTION: 发生异常会触发此函数
     * @return: void
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

        System.out.println();
        ctx.close();
        log.info(ctx.channel().id() + " 发生了错误,此连接被关闭" + "此时连通数量: " + CHANNEL_MAP.size());
        cause.printStackTrace();
    }
}
